//一个工具库，封装了使用three.js的操作

import * as THREE from './three/build/three.module.js'

import {OrbitControls} from './three/jsm/controls/OrbitControls.js'
import {GLTFLoader} from './three/jsm/loaders/GLTFLoader.js'

let scene
let camera
let renderer
let controls
let model, modelHasLoad, ground
let ambientLight, sunLight
const items = []
let isItemRun = false
let speedLong = 0, speedShort = 0 //当前传送带速度
const SPEED_ITEM_SHORT = 1.15 //物品调整速率
const SPEED_ITEM_LONG = 1.06 //物品调整速率

//物体碰撞检测
let pushOut = [false, false, false, false, false, false]
const ITEM_PUSHOUT_SPEED = 6.3 //物品推出速度

const clock = new THREE.Clock()
let animations, mixer

const LIGHT_RANGE = 30 //光照范围，无需更改
const PUSHER_RANGE = 34 //推杆推出的长度，单位cm
const PUSHER_SPEED = 54 //推杆线速度，单位cm/s
const PUSHER_DURATION = PUSHER_RANGE / PUSHER_SPEED
const BELT_SHORT_RANGE = 2 //上货端传送带长度，单位m
const BELT_LONG_RANGE = 3 //分拣端传送带长度，单位m

//场景
function initScene(){
    scene = new THREE.Scene()
}

//相机
function initCamera() {
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
    camera.position.set(-1, 15.5, 20.5)
    camera.lookAt(0, 10, 0)
}

//渲染器
function initRenderer() {
    renderer = new THREE.WebGLRenderer({antialias: true}) //抗锯齿
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setClearColor(0x333333) //背景色
    renderer.shadowMap.enabled = true //开启阴影，加上阴影渲染
}

//模型
function initModel(modelPath) {
    //GLB模型
    let loader = new GLTFLoader()
    loader.load(modelPath, (glb) => {
        model = glb.scene
        model.traverse((child) => {
            child.castShadow = true
            child.receiveShadow = true
        })
        modelHasLoad = true

        scene.add(glb.scene)

        //模型动画
        mixer = new THREE.AnimationMixer(model)

        animations = glb.animations
        for (let i = 0; i < 6; i ++){
            let action = mixer.clipAction(animations[i])
            action.setLoop(THREE.LoopPingPong)
            action.timeScale = PUSHER_SPEED / PUSHER_RANGE
        }
        mixer.clipAction(animations[6]).play()
        mixer.clipAction(animations[6]).paused = true
        mixer.clipAction(animations[7]).play()
        mixer.clipAction(animations[7]).paused = true
    })

    //场景
    let planeGeometry = new THREE.PlaneGeometry(4000, 4000)
    let planeMaterial = new THREE.MeshLambertMaterial({color: 0x305075})
    ground = new THREE.Mesh(planeGeometry, planeMaterial)
    ground.rotation.x = - Math.PI / 2
    ground.receiveShadow = true
    scene.add(ground)
}

//添加光照
function initLight() {
    //环境光
    ambientLight = new THREE.AmbientLight(0x555555)
    scene.add(ambientLight)

    //平行光
    sunLight = new THREE.DirectionalLight(0xffffee)
    sunLight.position.set(-50, 45, 30)
    sunLight.castShadow = true //开启阴影投射

    //设置阴影属性
    sunLight.shadow.camera.far = 100
    sunLight.shadow.camera.fov = 100
    sunLight.shadow.camera.near = 0.01
    sunLight.shadow.camera.left = - LIGHT_RANGE
    sunLight.shadow.camera.right = LIGHT_RANGE
    sunLight.shadow.camera.top = LIGHT_RANGE
    sunLight.shadow.camera.bottom = - LIGHT_RANGE
    sunLight.shadow.normalBias = 0.1
    sunLight.shadow.mapSize.width = 1024
    sunLight.shadow.mapSize.height = 1024
    scene.add(sunLight)
}

//控制器
function initControls() {
    controls = new OrbitControls(camera, renderer.domElement)
    controls.maxPolarAngle = Math.PI * 0.5 - 0.01 //限制转动角度
    controls.maxDistance = 100 //限制镜头最远距离
    controls.minDistance = 5 //限制镜头最近距离
}

//窗口大小改变时触发
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()

    renderer.setSize(window.innerWidth, window.innerHeight)
}

function animate() {
    requestAnimationFrame(animate)
    render()
}

function render() {
    let delta = clock.getDelta()

    if (modelHasLoad) {
        //播放动画
        mixer.update(delta)

        items.forEach((item, index) => {
            //推杆推动物体
            let pos_front = item.position.x - item.scale.x / 2
            let pos_back = item.position.x + item.scale.x / 2
            if (pos_front < -3 && pos_back > -6.5) { //推杆1
                if (pushOut[0]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }
            else if (pos_front < -7.5 && pos_back > -11) { //推杆2
                if (pushOut[1]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }
            else if (pos_front < -12 && pos_back > -15.5) { //推杆3
                if (pushOut[2]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }
            else if (pos_front < -16.5 && pos_back > -20) { //推杆4
                if (pushOut[3]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }
            else if (pos_front < -21 && pos_back > -24.5) { //推杆5
                if (pushOut[4]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }
            else if (pos_front < -25.5 && pos_back > -29) { //推杆6
                if (pushOut[5]) item.position.z += delta * ITEM_PUSHOUT_SPEED
            }

            //传送带物体移动
            let itemExist = true
            if (item.position.x >= -1) { //上货端
                if (index === items.length - 1) {
                    if (isItemRun) {
                        item.scale.x -= speedShort * delta * SPEED_ITEM_SHORT
                        item.translateX(speedShort * delta * SPEED_ITEM_SHORT / 2)
                    } else {
                        item.translateX(speedShort * delta * SPEED_ITEM_SHORT)
                    }
                } else {
                    item.translateX(speedShort * delta * SPEED_ITEM_SHORT)
                }
            }
            else if (item.position.x >= -30.5) { //分拣端
                item.translateX(speedLong * delta * SPEED_ITEM_LONG)
            }
            else { //离开传送带
                scene.remove(item)
                items.splice(index, 1)
                itemExist = false
            }

            //推出传送带
            if (item.position.z > 2.5 && itemExist) {
                scene.remove(item)
                items.splice(index, 1)
            }
        })

        renderer.render(scene, camera)
    }
}

function init(modelPath) {
    initScene()
    initCamera()
    initRenderer()
    initModel(modelPath)
    initLight()
    initControls()

    document.body.appendChild(renderer.domElement)
    window.onresize = onWindowResize

    animate()
}

//以下函数为工具类函数
function push(num, holdTime) {
    let action = mixer.clipAction(animations[num - 1])
    action.play()

    if (holdTime < 0.2){
        holdTime = 0.2
    }

    //推出物体碰撞检测
    setTimeout(() => {
        pushOut[num - 1] = true

        setTimeout(() => {
            pushOut[num - 1] = false
        }, PUSHER_DURATION * 667)
    }, PUSHER_DURATION * 333)

    setTimeout(() => {
        action.paused = true

        setTimeout(() => {
            action.paused = false

            setTimeout(() => {
                action.stop()
            }, PUSHER_DURATION * 1000)
        }, holdTime * 1000)
    }, PUSHER_DURATION * 1000)
}

function rollShort(speed) {
    let action = mixer.clipAction(animations[6])
    action.timeScale = 0.5 / (BELT_SHORT_RANGE / (speed / 60))
    speedShort = -(speed / 6)
    action.paused = false
}
function rollLong(speed) {
    let action = mixer.clipAction(animations[7])
    action.timeScale = 0.5 / (BELT_LONG_RANGE / (speed / 60))
    speedLong = -(speed / 6)
    action.paused = false
}

function stopShort() {
    let action = mixer.clipAction(animations[6])
    speedShort = 0
    action.paused = true
}
function stopLong() {
    let action = mixer.clipAction(animations[7])
    speedLong = 0
    action.paused = true
}

function safeParseJSON(str) {
    if (typeof str == 'string') {
        try {
            let obj = JSON.parse(str);
            if (typeof obj == 'object' && obj ){
                return obj;
            }else{
                return false;
            }

        } catch(e) {
            return false;
        }
    }
    return false;
}

function analysisReport(data) {
   if (data[0] === 5) {
       analysisCurrentStatus(data)
   }
   else if (data[0] === 6) {
       analysisCrashReport(data)
   }
   else if (data[0] === 8) {
       analysisEyeReport(data[2])
   }
   //此处开始为前后端约定的反馈消息
   else if (data[0] === 9) { //皮带
        if (data[3] === 1) {
            log('触发失败，未能连接机器', 'danger')
            if (data[1] === 1){
                document.querySelector('#belt-s').removeAttribute('disabled')
            } else {
                document.querySelector('#belt-l').removeAttribute('disabled')
            }
        } else {
            if (data[1] === 1){ //上货端
                let belt = document.querySelector('#belt-s')
                if (data[2] === 1){ //启动
                    rollShort(data[4])
                    log('上货端传送带启动成功', 'success')
                    belt.removeAttribute('disabled')
                    belt.classList.toggle('danger')
                    belt.innerHTML = '停止'
                    belt.setAttribute('data-roll', 'true')
                    document.querySelector('#belt-s-range').setAttribute('disabled', '')
                }
                else {
                    stopShort()
                    log('上货端传送带停止成功', 'success')
                    belt.removeAttribute('disabled')
                    belt.classList.toggle('danger')
                    belt.innerHTML = '启动'
                    belt.setAttribute('data-roll', 'false')
                    document.querySelector('#belt-s-range').removeAttribute('disabled')
                }
            }
            else if (data[1] === 2){ //分拣端
                let belt = document.querySelector('#belt-l')
                if (data[2] === 1){
                    rollLong(data[4])
                    log('分拣端传送带启动成功', 'success')
                    belt.removeAttribute('disabled')
                    belt.classList.toggle('danger')
                    belt.innerHTML = '停止'
                    belt.setAttribute('data-roll', 'true')
                    document.querySelector('#belt-l-range').setAttribute('disabled', '')
                }
                else {
                    stopLong()
                    log('分拣端传送带停止成功', 'success')
                    belt.removeAttribute('disabled')
                    belt.classList.toggle('danger')
                    belt.innerHTML = '启动'
                    belt.setAttribute('data-roll', 'false')
                    document.querySelector('#belt-l-range').removeAttribute('disabled')
                }
            }
        }
   }
   else if (data[0] === 10) { //推杆
      if (data[3] === 1) {
          log('触发失败，未能连接机器', 'danger')
          document.querySelectorAll('button.push')[data[1] - 1].removeAttribute('disabled')
      } else {
          push(data[1], data[4] / 10)
          log(data[1] + '号推杆触发成功', 'success')
          document.querySelectorAll('button.push')[data[1] - 1].removeAttribute('disabled')
      }
   }
   else if (data[0] === 11) { //查询状态
       if (data[1] === 1) {
           log('查询状态失败，未能连接机器', 'danger')
       } else {
           log('查询状态成功', 'success')
       }
   }
}

function analysisCurrentStatus(data) {
    //推杆状态
    let status3 = data[4]
    for (let i = 0; i < 6; i++) {
        let status_pusher = (status3 & 1) === 1;
        if (!status_pusher) {
            log((i + 1) + '号推杆未就绪', 'danger')
        }
        status3 >>>= 1;
    }

    //变频器状态
    analysisFrequency(data[5], 1)
    analysisFrequency(data[6], 2)
}
function analysisFrequency(code, num) {
    let report = num + '号变频器'
    switch (code) {
        case 0:
            return
        case 1:
            report += '过电流 OC'
            break
        case 2:
            report += '过电压 OV'
            break
        case 3:
            report += 'IGBT过热 OH1'
            break
        case 5:
            report += '过负载 OL'
            break
        case 6:
            report += '电机过负载 OL1'
            break
        case 7:
            report += '过转矩 OL2'
            break
        case 8:
            report += '外部异常 EF'
            break
        case 9:
            report += '加速中过电流 OCA'
            break
        case 10:
            report += '减速中过电流 OCD'
            break
        case 11:
            report += '恒速中过电流 OCN'
            break
        case 14:
            report += '输入电源欠相 PHL'
            break
        case 16:
            report += '不适用自动加减速设定 CFA'
            break
        case 17:
            report += '软件与参数密码保护 CODE'
            break
        case 18:
            report += 'CPU写入有问题 CF1.0'
            break
        case 19:
            report += 'CPU读出有问题 CF2.0'
            break
        case 20:
            report += 'CC，OC保护线路有问题 HPF1'
            break
        case 21:
            report += 'OV保护线路有问题 HPF2'
            break
        case 23:
            report += 'OC保护线路有问题 HPF4'
            break
        case 24:
            report += 'U相硬件线路异常 CF3.0'
            break
        case 25:
            report += 'V相硬件线路异常 CF3.1'
            break
        case 26:
            report += 'W相硬件线路异常 CF3.2'
            break
        case 27:
            report += 'DCBUS硬件线路异常 CF3.3'
            break
        case 28:
            report += 'OH1硬件线路异常 CF3.4'
            break
        default:
            log('机器发出未知报警', 'danger')
            return

    }
    log(report, 'danger')
}
function analysisCrashReport(data) {
    //推杆状态
    let status2 = data[2]
    for (let i = 0; i < 6; i++) {
        let status_pusher = (status2 & 1) === 1;
        if (!status_pusher) {
            log((i + 1) + '号推杆发生故障', 'danger')
        }
        status2 >>>= 1;
    }

    //推杆超时
    let status3 = data[3]
    for (let i = 0; i < 6; i++) {
        let status_pusher = (status3 & 1) === 0;
        if (!status_pusher) {
            log((i + 1) + '号推杆未动作', 'danger')
        }
        status3 >>>= 1;
    }

    //变频器状态
    analysisFrequency(data[5], 1)
    analysisFrequency(data[6], 2)
}
function analysisEyeReport(status) {
    if (status === 1) {
        let geometry = new THREE.BoxGeometry(1, 1, 1)
        let material = new THREE.MeshLambertMaterial({color: 0x22c72c})
        let item = new THREE.Mesh(geometry, material)
        item.position.set(9.5, 6.06, 1)
        item.scale.x = 0.0001
        item.castShadow = true
        scene.add(item)
        items.push(item)
        isItemRun = true
        log('检测到物体经过', 'primary')
    }
    else if (status === 2) {
        isItemRun = false
        log('检测到物体已通过', 'primary')
    }
    else {
        log('光眼报告出错', 'danger')
    }
}
function log(text, level){
    info.innerHTML = '<p class="info-' + level + '">' + text + '</p>' + info.innerHTML
}

export {init, push, rollShort, rollLong, stopShort, stopLong, safeParseJSON, analysisReport}